{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7f124641",
   "metadata": {},
   "source": [
    "# LCEL (대화내용 기억하기): 메모리 추가\n",
    "\n",
    "임의의 체인에 메모리를 추가하는 방법을 보여줍니다. 현재 메모리 클래스를 사용할 수 있지만 수동으로 연결해야 합니다\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "75fd047c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "43fca8db",
   "metadata": {},
   "outputs": [],
   "source": [
    "from operator import itemgetter\n",
    "from langchain.memory import ConversationBufferMemory\n",
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "from langchain_core.runnables import RunnableLambda, RunnablePassthrough\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "\n",
    "# ChatOpenAI 모델을 초기화합니다.\n",
    "model = ChatOpenAI()\n",
    "\n",
    "# 대화형 프롬프트를 생성합니다. 이 프롬프트는 시스템 메시지, 이전 대화 내역, 그리고 사용자 입력을 포함합니다.\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", \"You are a helpful chatbot\"),\n",
    "        MessagesPlaceholder(variable_name=\"chat_history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4921dd5c",
   "metadata": {},
   "source": [
    "대화내용을 저장할 메모리인 `ConversationBufferMemory` 생성하고 `return_messages` 매개변수를 `True`로 설정하여, 생성된 인스턴스가 메시지를 반환하도록 합니다.\n",
    "\n",
    "- `memory_key` 설정: 추후 Chain 의 `prompt` 안에 대입될 key 입니다. 변경하여 사용할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7d6c97b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 대화 버퍼 메모리를 생성하고, 메시지 반환 기능을 활성화합니다.\n",
    "memory = ConversationBufferMemory(return_messages=True, memory_key=\"chat_history\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2d6b029",
   "metadata": {},
   "source": [
    "저장된 대화기록을 확인합니다. 아직 저장하지 않았으므로, 대화기록은 비어 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "07317c49",
   "metadata": {},
   "outputs": [],
   "source": [
    "memory.load_memory_variables({})  # 메모리 변수를 빈 딕셔너리로 초기화합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4f9bbc0",
   "metadata": {},
   "source": [
    "`RunnablePassthrough.assign`을 사용하여 `chat_history` 변수에 `memory.load_memory_variables` 함수의 결과를 할당하고, 이 결과에서 `chat_history` 키에 해당하는 값을 추출합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ce20e13f",
   "metadata": {},
   "outputs": [],
   "source": [
    "runnable = RunnablePassthrough.assign(\n",
    "    chat_history=RunnableLambda(memory.load_memory_variables)\n",
    "    | itemgetter(\"chat_history\")  # memory_key 와 동일하게 입력합니다.\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ab75a2f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "runnable.invoke({\"input\": \"hi\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dc636793",
   "metadata": {},
   "outputs": [],
   "source": [
    "runnable.invoke({\"input\": \"hi\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54745998",
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", \"You are a helpful chatbot\"),\n",
    "        MessagesPlaceholder(variable_name=\"chat_history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86174a27",
   "metadata": {},
   "source": [
    "`runnable` 에 첫 번째 대화를 시작합니다.\n",
    "\n",
    "- `input`: 사용자 입력 대화가 전달됩니다.\n",
    "- `chat_history`: 대화 기록이 전달됩니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4ebf718",
   "metadata": {},
   "outputs": [],
   "source": [
    "runnable.invoke({\"input\": \"hi!\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ded5e90",
   "metadata": {},
   "outputs": [],
   "source": [
    "chain = runnable | prompt | model"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f1e58a9",
   "metadata": {},
   "source": [
    "첫 번째 대화를 진행합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c3f9232",
   "metadata": {},
   "outputs": [],
   "source": [
    "# chain 객체의 invoke 메서드를 사용하여 입력에 대한 응답을 생성합니다.\n",
    "response = chain.invoke({\"input\": \"만나서 반갑습니다. 제 이름은 테디입니다.\"})\n",
    "print(response.content)  # 생성된 응답을 출력합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "df38fc8d",
   "metadata": {},
   "outputs": [],
   "source": [
    "memory.load_memory_variables({})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f547b79",
   "metadata": {},
   "source": [
    "`memory.save_context` 함수는 입력 데이터(`inputs`)와 응답 내용(`response.content`)을 메모리에 저장하는 역할을 합니다. 이는 AI 모델의 학습 과정에서 현재 상태를 기록하거나, 사용자의 요청과 시스템의 응답을 추적하는 데 사용될 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2183aeb3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 입력된 데이터와 응답 내용을 메모리에 저장합니다.\n",
    "memory.save_context(\n",
    "    {\"human\": \"만나서 반갑습니다. 제 이름은 테디입니다.\"}, {\"ai\": response.content}\n",
    ")\n",
    "\n",
    "# 저장된 대화기록을 출력합니다.\n",
    "memory.load_memory_variables({})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c83550d6",
   "metadata": {},
   "source": [
    "이름을 기억하고 있는지 추가 질의합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "966194ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 이름을 기억하고 있는지 추가 질의합니다.\n",
    "response = chain.invoke({\"input\": \"제 이름이 무엇이었는지 기억하세요?\"})\n",
    "# 답변을 출력합니다.\n",
    "print(response.content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8af5281a",
   "metadata": {},
   "source": [
    "## 커스텀 ConversationChain 구현 예시"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b1ffe790",
   "metadata": {},
   "outputs": [],
   "source": [
    "from operator import itemgetter\n",
    "from langchain.memory import ConversationBufferMemory, ConversationSummaryMemory\n",
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "from langchain_core.runnables import RunnableLambda, RunnablePassthrough, Runnable\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "\n",
    "# ChatOpenAI 모델을 초기화합니다.\n",
    "llm = ChatOpenAI(model_name=\"gpt-4.1-mini\", temperature=0)\n",
    "\n",
    "# 대화형 프롬프트를 생성합니다. 이 프롬프트는 시스템 메시지, 이전 대화 내역, 그리고 사용자 입력을 포함합니다.\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", \"You are a helpful chatbot\"),\n",
    "        MessagesPlaceholder(variable_name=\"chat_history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# 대화 버퍼 메모리를 생성하고, 메시지 반환 기능을 활성화합니다.\n",
    "memory = ConversationBufferMemory(return_messages=True, memory_key=\"chat_history\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ef76b36",
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyConversationChain(Runnable):\n",
    "\n",
    "    def __init__(self, llm, prompt, memory, input_key=\"input\"):\n",
    "\n",
    "        self.prompt = prompt\n",
    "        self.memory = memory\n",
    "        self.input_key = input_key\n",
    "\n",
    "        self.chain = (\n",
    "            RunnablePassthrough.assign(\n",
    "                chat_history=RunnableLambda(self.memory.load_memory_variables)\n",
    "                | itemgetter(memory.memory_key)  # memory_key 와 동일하게 입력합니다.\n",
    "            )\n",
    "            | prompt\n",
    "            | llm\n",
    "            | StrOutputParser()\n",
    "        )\n",
    "\n",
    "    def invoke(self, query, configs=None, **kwargs):\n",
    "        answer = self.chain.invoke({self.input_key: query})\n",
    "        self.memory.save_context(inputs={\"human\": query}, outputs={\"ai\": answer})\n",
    "        return answer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "745b377c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# ChatOpenAI 모델을 초기화합니다.\n",
    "llm = ChatOpenAI(model_name=\"gpt-4.1-mini\", temperature=0)\n",
    "\n",
    "# 대화형 프롬프트를 생성합니다. 이 프롬프트는 시스템 메시지, 이전 대화 내역, 그리고 사용자 입력을 포함합니다.\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", \"You are a helpful chatbot\"),\n",
    "        MessagesPlaceholder(variable_name=\"chat_history\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# 대화 버퍼 메모리를 생성하고, 메시지 반환 기능을 활성화합니다.\n",
    "memory = ConversationBufferMemory(return_messages=True, memory_key=\"chat_history\")\n",
    "\n",
    "# 요약 메모리로 교체할 경우\n",
    "# memory = ConversationSummaryMemory(\n",
    "#     llm=llm, return_messages=True, memory_key=\"chat_history\"\n",
    "# )\n",
    "\n",
    "conversation_chain = MyConversationChain(llm, prompt, memory)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5e7a320a",
   "metadata": {},
   "outputs": [],
   "source": [
    "conversation_chain.invoke(\"안녕하세요? 만나서 반갑습니다. 제 이름은 테디 입니다.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64cae8a5",
   "metadata": {},
   "outputs": [],
   "source": [
    "conversation_chain.invoke(\"제 이름이 뭐라고요?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7ba9187d",
   "metadata": {},
   "outputs": [],
   "source": [
    "conversation_chain.invoke(\"앞으로는 영어로만 답변해주세요 알겠어요?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18b6354f",
   "metadata": {},
   "outputs": [],
   "source": [
    "conversation_chain.invoke(\"제 이름을 다시 한 번 말해주세요\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "253f6162",
   "metadata": {},
   "outputs": [],
   "source": [
    "conversation_chain.memory.load_memory_variables({})[\"chat_history\"]"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
